package com.mapforge.gui.frames.map;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.BevelBorder;
import javax.swing.filechooser.FileFilter;

import com.mapforge.ActionCommands;
import com.mapforge.Constants;
import com.mapforge.filechooser.DefaultTilesetFileFilter;
import com.mapforge.filechooser.MapEditorFileChooser;
import com.mapforge.gui.map.TilesetPanel;
import com.mapforge.io.DefaultTilesetIOHandler;
import com.mapforge.io.IOHandlerInterface;
import com.mapforge.listener.TileMapCreationEvent;
import com.mapforge.listener.TileMapCreationListener;
import com.mapforge.listener.TilesetChangeEvent;
import com.mapforge.listener.TilesetChangeListener;
import com.mapforge.listener.TilesetIOHandlerChangeEvent;
import com.mapforge.listener.TilesetIOHandlerChangeListener;
import com.mapforge.map.TileMap;
import com.mapforge.map.Tileset;
import com.mapforge.util.FileUtil;

/** */
public class NewMapDialog extends JDialog implements
		TilesetIOHandlerChangeListener, ActionListener {
	/**
	 * Default serial version ID, here to remove warnings.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * 
	*/
	private TileMap createdMap;

	/**
	 * Listeners notified when a new map is created.
	 */
	private List<TileMapCreationListener> mapCreationListeners;
	/**
	 * Listeners notified whenever a newly created map is assigned a tileset.
	 */
	private List<TilesetChangeListener> tilesetChangeListeners;

	private SpinnerNumberModel widthModel;
	private SpinnerNumberModel heightModel;
	private SpinnerNumberModel tileWidthModel;
	private SpinnerNumberModel tileHeightModel;

	private JSpinner spnTileWidth;
	private JSpinner spnTileHeight;

	private JTextField txtMapName;
	private JTextField txtTilesetName;

	private JFileChooser chooser;

	private TilesetPanel tilesetPanel;
	private JScrollPane tilesetPanelScroll;
	private JTextField txtImgPath;

	private File selectedFile;
	private Tileset tileset;
	private BufferedImage tilesetImage;

	private DefaultTilesetFileFilter imgTilesetFilter;

	Box mapPropertiesBox;

	protected NewMapDialog(JFrame parent,
			List<TileMapCreationListener> mapCreationListeners,
			List<TilesetChangeListener> tilesetChangeListeners) {
		super(parent, "New Map");
		this.addWindowListener(new WindowCloseListener());
		this.mapCreationListeners = mapCreationListeners;
		this.tilesetChangeListeners = tilesetChangeListeners;
		chooser = MapEditorFileChooser.getHandle();

		imgTilesetFilter = new DefaultTilesetFileFilter(
				new DefaultTilesetIOHandler(), "Image Files and .tset");
		chooser.addChoosableFileFilter(imgTilesetFilter);
		chooser.setFileFilter(imgTilesetFilter);

		tilesetPanel = new TilesetPanel();
		txtImgPath = new JTextField("No Image or Tileset specified", 20);
		txtImgPath.setEnabled(false);
		initPanes();
		pack();
		setLocationRelativeTo(parent);
	}

	public void initPanes() {
		this.setLayout(new BorderLayout(5, 5));
		Box detailPane = Box.createVerticalBox();
		mapPropertiesBox = Box.createVerticalBox();
		Box tilesetDetailsBox = Box.createVerticalBox();
		Box mapDetailsBox = Box.createVerticalBox();
		tilesetDetailsBox.setBorder(BorderFactory
				.createBevelBorder(BevelBorder.RAISED));
		mapDetailsBox.setBorder(BorderFactory
				.createBevelBorder(BevelBorder.RAISED));

		FlowLayout flowLay = new FlowLayout(FlowLayout.LEFT, 5, 5);

		JPanel namePanel = new JPanel(flowLay);
		namePanel.add(new JLabel("Map Name:"));
		namePanel.add(txtMapName = new JTextField(10));
		mapDetailsBox.add(namePanel);

		JPanel widthPanel = new JPanel(flowLay);
		widthPanel.add(new JLabel("Width:"));
		JSpinner spinW = new JSpinner(widthModel = new SpinnerNumberModel(1, 1,
				Constants.MAX_MAP_DIMENSION.width, 1));
		widthPanel.add(spinW);
		mapDetailsBox.add(widthPanel);

		JPanel heightPanel = new JPanel(flowLay);
		heightPanel.add(new JLabel("Height:"));
		JSpinner spinH = new JSpinner(heightModel = new SpinnerNumberModel(1,
				1, Constants.MAX_MAP_DIMENSION.height, 1));
		heightPanel.add(spinH);
		mapDetailsBox.add(heightPanel);

		JPanel tilesetNamePanel = new JPanel(flowLay);
		tilesetNamePanel.add(new JLabel("Tileset Name:"));
		tilesetNamePanel.add(txtTilesetName = new JTextField(10));
		txtTilesetName.setEnabled(false);
		tilesetDetailsBox.add(tilesetNamePanel);

		JPanel tileWidthPanel = new JPanel(flowLay);
		tileWidthPanel.add(new JLabel("Tile Width:"));
		spnTileWidth = new JSpinner(tileWidthModel = new SpinnerNumberModel(
				Constants.DEFAULT_TILE_DIMENSION.width, 1,
				Constants.MAX_TILE_DIMENSION.width, 1));
		spnTileWidth.setEnabled(false);
		tileWidthPanel.add(spnTileWidth);
		tilesetDetailsBox.add(tileWidthPanel);

		JPanel tileHeightPanel = new JPanel(flowLay);
		tileHeightPanel.add(new JLabel("Tile Height:"));
		spnTileHeight = new JSpinner(tileHeightModel = new SpinnerNumberModel(
				Constants.DEFAULT_TILE_DIMENSION.height, 1,
				Constants.MAX_TILE_DIMENSION.height, 1));
		spnTileHeight.setEnabled(false);
		tileHeightPanel.add(spnTileHeight);
		tilesetDetailsBox.add(tileHeightPanel);

		JPanel createPane = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5));
		createPane.add(createButton("Ok"));
		createPane.add(createButton("Cancel"));

		detailPane.add(mapDetailsBox);
		detailPane.add(tilesetDetailsBox);
		add(createPane, BorderLayout.SOUTH);

		add(detailPane, BorderLayout.EAST);

		tilesetPanelScroll = new JScrollPane(tilesetPanel);
		tilesetPanelScroll
				.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
		tilesetPanelScroll
				.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		tilesetPanel.add(new JLabel("No Image or Tileset"));
		tilesetPanelScroll.setPreferredSize(new Dimension(300, 300));
		tilesetPanelScroll.setBorder(BorderFactory
				.createBevelBorder(BevelBorder.LOWERED));
		JPanel scrollWrapper = new JPanel(new FlowLayout(FlowLayout.CENTER, 5,
				5));
		scrollWrapper.add(tilesetPanelScroll);
		add(scrollWrapper, BorderLayout.CENTER);

		JPanel imgControl = new JPanel(flowLay);
		imgControl.add(new JLabel("Map's Tileset or Image:"));
		imgControl.add(txtImgPath);
		imgControl.add(createButton("Browse..."));
		add(imgControl, BorderLayout.NORTH);
	}

	private void allowTilesetEntry(boolean val) {
		this.txtTilesetName.setText("");
		this.spnTileWidth.setValue(1);
		this.spnTileHeight.setValue(1);
		this.txtTilesetName.setEnabled(val);
		this.spnTileHeight.setEnabled(val);
		this.spnTileWidth.setEnabled(val);
	}

	protected void chooseMapImage() {
		int opt = chooser.showOpenDialog(this);
		if (opt == JFileChooser.APPROVE_OPTION) {
			selectedFile = chooser.getSelectedFile();
			if (selectedFile.exists()) {
				try {
					BufferedImage img = null;
					FileFilter filter = chooser.getFileFilter();
					if (filter instanceof DefaultTilesetFileFilter) {
						DefaultTilesetFileFilter specialFilter = (DefaultTilesetFileFilter) filter;
						// if not an image assume is a valid tileset
						if (!DefaultTilesetFileFilter.getImageFileFilter()
								.accept(selectedFile)) {
							IOHandlerInterface<Tileset> tilesetIO = specialFilter
									.getIOHandler();
							tileset = tilesetIO.read(selectedFile);
						}
						// image file filter accepted, read as an image
						else {
							tilesetImage = img = ImageIO.read(selectedFile);
							tilesetPanel.setTileset(null, tilesetImage);
							tilesetPanel.revalidate();
						}
					}
					// selected file filter failed to read selected file, so its
					// not an image or a format that filter recognizes. Try
					// all file filters.
					else if (img == null) {
						FileFilter fileFilters[] = chooser
								.getChoosableFileFilters();
						for (int i = 0; i < fileFilters.length; i++) {
							if (filter instanceof DefaultTilesetFileFilter) {
								DefaultTilesetFileFilter specialFilter = (DefaultTilesetFileFilter) filter;
								if (!specialFilter.accept(selectedFile)) {
									IOHandlerInterface<Tileset> tilesetIO = specialFilter
											.getIOHandler();
									tileset = tilesetIO.read(selectedFile);
								}
							}
						}
					}
					// if we loaded the tileset successful somehow, need to
					// display its image
					if (tileset != null) {
						this.txtTilesetName.setText(tileset.getName());
						tilesetPanel.setTileset(tileset, tilesetImage);
						tilesetPanel.revalidate();
						// set GUI fields to reflect tileset's values
						txtImgPath.setText(selectedFile.getAbsolutePath());
						this.txtTilesetName.setText(tileset.getName());
						this.tileWidthModel.setValue(tileset.getTileWidth());
						tileHeightModel.setValue(tileset.getTileHeight());
						this.txtTilesetName.setEnabled(true);
					}
					// allow entry of values into tileset
					else if (img != null) {
						this.allowTilesetEntry(true);
						txtImgPath.setText(selectedFile.getAbsolutePath());
					}
					// tileset is null and icon is null, failure.
					else if (img == null) {
						this.allowTilesetEntry(false);
						JOptionPane.showMessageDialog(this,
								"Failed to load selected file.\nUnrecognized extension: \""
										+ FileUtil.getExt(selectedFile) + "\"");
					}
				} catch (Exception e) {
					this.allowTilesetEntry(false);
					JOptionPane.showMessageDialog(this,
							"Exception loading file " + selectedFile.getName());
				}
			} else {
				this.allowTilesetEntry(true);
				JOptionPane.showMessageDialog(this, "Couldn't locate "
						+ selectedFile.getName());
			}
		}
	}

	private JButton createButton(String s) {
		JButton b = new JButton(s);
		b.addActionListener(this);
		return b;
	}

	protected void createMap() {
		String mapName = txtMapName.getText();
		if (tileset == null && tilesetImage == null) {
			JOptionPane
					.showMessageDialog(
							this,
							"Missing valid tileset or image file.\nLocate a valid tileset or image by pressing \"Browse\"");
			return;
		}
		if (mapName.equals("")) {
			JOptionPane.showMessageDialog(this, "Missing map name");
			createdMap = null;
			return;
		}

		String tilesetName = txtTilesetName.getText();
		if (tilesetName.equals("")) {
			JOptionPane.showMessageDialog(this, "Missing tileset name");
			createdMap = null;
			return;
		}

		int w = widthModel.getNumber().intValue();
		int h = heightModel.getNumber().intValue();
		int tileW = tileWidthModel.getNumber().intValue();
		int tileH = tileHeightModel.getNumber().intValue();

		if (w < 1 || h < 1) {
			JOptionPane.showMessageDialog(this, "Map dimenions must exceed 0");
			createdMap = null;
			return;
		} else if (tileW < 1 || tileH < 1) {
			JOptionPane.showMessageDialog(this, "Tile dimenions must exceed 0");
			createdMap = null;
			return;
		}

		try {
			// create a new tileset
			if (tileset == null && tilesetImage != null) {
				int tilesetHorizontalTiles = tilesetImage.getWidth() / tileW;
				int tilesetVerticalTiles = tilesetImage.getHeight() / tileH;
				int tilesetTotalTiles = tilesetHorizontalTiles
						* tilesetVerticalTiles;

				if (tilesetHorizontalTiles < 1 || tilesetVerticalTiles < 1) {
					JOptionPane.showMessageDialog(this,
							"Tile dimensions cannot exceed image dimensions");
					return;
				} else if (tilesetTotalTiles > Constants.MAX_TILESET_TILES) {
					// int sqrtMaxTiles =
					// (int)Math.sqrt(Constants.MAX_TILESET_TILES);
					// int minWidth = tilesetImage.getWidth()/sqrtMaxTiles;
					// int minHeight = tilesetImage.getHeight()/sqrtMaxTiles;
					// TODO display a warning that the tile dimensions will
					// generate a LOT of tiles
				}
				createdMap = new TileMap(mapName, w, h, tilesetImage, tileW,
						tileH);
				createdMap.getTileset().setName(tilesetName);
			}
			// use specified tileset
			else if (tileset != null) {
				createdMap = new TileMap(tileset, mapName, w, h);
			}

			// Notify listeners
			TileMapCreationEvent mapCreationEvent = new TileMapCreationEvent(
					this, createdMap, tilesetImage);
			TilesetChangeEvent tilesetChangeEvent = new TilesetChangeEvent(
					this, createdMap.getTileset());
			this.fireNewMapCreationEvent(mapCreationEvent);
			this.fireTilesetChangeEvent(tilesetChangeEvent);

			tilesetImage = null;
		} catch (Exception e) {
			JOptionPane.showMessageDialog(this,
					"Could not create map.\nDetails:" + e);
			createdMap = null;
			e.printStackTrace();
		}
	}

	public static BufferedImage getImage(String path) {
		Image image = new ImageIcon(path).getImage();
		int w = image.getWidth(null);
		int h = image.getHeight(null);
		if (w == -1 || h == -1) {
			return null;
		}
		// Creating a buffered image with transparency supported.
		BufferedImage buffImg = new BufferedImage(w, h,
				Transparency.TRANSLUCENT);
		Graphics2D g = buffImg.createGraphics();
		g.drawImage(image, 0, 0, null);
		g.dispose();
		return buffImg;
	}

	public void setVisible(boolean visible) {
		if (visible) {
			createdMap = null;
		}
		super.setVisible(visible);
	}

	private void resetFields() {
		allowTilesetEntry(false);
		txtMapName.setText("");
		tilesetImage = null;
		widthModel.setValue(1);
		this.tileWidthModel.setValue(1);
		this.tileHeightModel.setValue(1);
		heightModel.setValue(1);
		tilesetPanel.removeTileset();
		tilesetPanel.removeAll();
		tilesetPanel.add(new JLabel("No Image or Tileset"));
		txtImgPath.setText("No Image or Tileset");
	}

	/**
	 *  
	 */
	public TileMap getCreatedMap() {
		setVisible(true);
		return createdMap;
	}

	public void addMapCreationListener(TileMapCreationListener listener) {
		mapCreationListeners.add(listener);
	}

	public void clearMapCreationListeners() {
		mapCreationListeners.clear();
	}

	/** */
	protected void fireNewMapCreationEvent(TileMapCreationEvent e) {
		for (int i = 0; i < mapCreationListeners.size(); i++) {
			mapCreationListeners.get(i).tileMapCreationPerformed(e);
		}
	}

	protected void fireTilesetChangeEvent(TilesetChangeEvent e) {
		for (TilesetChangeListener listener : tilesetChangeListeners) {
			listener.tilesetChangePerformed(e);
		}
	}

	public void actionPerformed(ActionEvent e) {
		String s = e.getActionCommand();
		if (s.equals(ActionCommands.NEW_MAP_CMD)) {
			setLocationRelativeTo(NewMapDialog.this.getParent());
			setVisible(true);
		} else if (s.equals("Ok")) {
			createMap();
			if (createdMap != null) {
				resetFields();
				setVisible(false);
			}
		} else if (s.equals("Cancel")) {
			resetFields();
			setVisible(false);
		} else if (s.equals("Browse...")) {
			chooseMapImage();
		}

	}

	/**
	 * If there's a change in the tileset IO handler, this dialog must be
	 * notified in order to change what filter its using for when the user is
	 * browsing files.
	 */
	public void ioHandlerChangePerformed(TilesetIOHandlerChangeEvent e) {
		imgTilesetFilter.setIOHandler(e.getNewHandler());
	}

	private class WindowCloseListener extends WindowAdapter {
		public void windowClosing(WindowEvent e) {
			resetFields();
		}
	}
}
